#    One Room And A Goblin Invasion
#    Copyright (C) 2014  Jannik Haberbosch
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.


import math
import random
import copy

import pygame
pygame.init()

from code.Creature import Creature
from code.Timer import Timer
from code.CollisionDetectionRectangle import CollisionDetectionRectangle

GOBLIN_STANDING = "standing"
GOBLIN_WALKING = "walking"


class Goblin( Creature ):

	counter = 0
	drop_garbage_max = 250
	def __init__( self,
		animation_walking,
		animation_standing,
		walking_time_in_frames_min,
		walking_time_in_frames_max ):
		"""

		"""
		super( Goblin, self ).__init__()

		self.animation_walking = animation_walking
		self.animation_standing = animation_standing
		self.current_animation = self.animation_walking

		self.image = self.current_animation.image
		self.rect = self.current_animation.rect

		self.center = self.current_animation.rect.center

		self.state = GOBLIN_WALKING

		self.walking_time_in_frames_min = walking_time_in_frames_min
		self.walking_time_in_frames_max = walking_time_in_frames_max
		self.pick_new_walking_time()

		self.timer_walking_time = Timer(
			self.walking_time_in_frames,
			1
		)

		self.angle = self.current_animation.angle
		self.degrees_to_change = 0

		self.speed = random.randint( 1, 7 )
		self.vector_x = 0
		self.vector_y = 0
		self.position_center = [ self.rect.center[0], self.rect.center[1] ]

		# Collision detection rectangles.
		self.collision_detection_rectangle_front = CollisionDetectionRectangle(
			self.rect.width - 10,
			self.rect.height - 10,
			[ self.rect.centerx + 5, self.rect.centery ]
		)
		self.collision_detection_rectangle_right = CollisionDetectionRectangle(
			self.rect.width  ,
			self.rect.height ,
			[ self.rect.centerx + 10, self.rect.centery + 10 ]
		)
		self.collision_detection_rectangle_left = CollisionDetectionRectangle(
			self.rect.width ,
			self.rect.height ,
			[ self.rect.centerx + 10, self.rect.centery + 10 ]
		)

		self.perfect_angle_found = False
		#self.look_ahead( 100 )
		self.deg_amount = 2
		self.already_turning = False

		self.turn_left = False
		self.turn_right = False

		# A boolean that indicates that the goblin is disappearing because it
		# has been hit by a projectile.
		self.dying = False
		
		# Once this variable has the value 0, the sprite will be removed from
		# it's corresponding sprite group.
		self.alpha_dying = 255

		# Once this variable is set to False, the goblin can move around freely
		# by its own choice.
		# Otherwise it walks straight into the room only directed by the angle.
		self.walking_into_the_room = True
		self.walking_into_the_room_time = 150 // self.speed
		self.walking_into_the_room_timer = Timer(
			self.walking_into_the_room_time,
			1,
			False
		)


	def maybe_drop_garbage( self ):
		"""Returns True if some garbage should be dropped onto the floor.

		"""
		if self.walking_into_the_room or self.dying:
			return False
			
		if random.randint( 1, Goblin.drop_garbage_max ) == 1:
			return True
		else:
			return False


	def update( self, rectangular_furniture, lines, player_projectiles ):
		"""Updates the object.

		"""
		# First check whether the goblin is still entering the room.
		if self.walking_into_the_room:
			if self.walking_into_the_room_timer.update() == True:
				# The goblin has walked into the room.
				self.walking_into_the_room = False
			else:
				# The goblin is still walking into the room.
				if not self.dying:
					self.walk()

					# Update all animations.
					self.animation_walking.update()
					self.animation_standing.update()

				# Set the new angle.
				self.angle = self.current_animation.angle
				
				# Set the new image and rect.
				self.image = self.current_animation.image
				self.rect = self.current_animation.rect

				# Also check whether the Goblin is dying or has actually died.
				if self.dying:
		
					# It is dying.
					if self.update_dying():
			
						# It has disappeared which means it is dead.  Remove it from the
						# sprite group.
						self.kill()
				
					return				


				# We immediately return.
				return

				
		# Secondly check whether the Goblin is dying or has actually died.
		if self.dying:
		
			# It is dying.
			if self.update_dying():
			
				# It has disappeared which means it is dead.  Remove it from the
				# sprite group.
				self.kill()
				
			return


		# Watch out for projectiles shot by the player.
		if self.watch_out_for_projectiles(
			200,
			player_projectiles ):
			if not self.already_turning:
				self.degrees_to_change = self.change_direction( 1 )
				self.turn_which_way( rectangular_furniture, lines )
			self.deg_amount = 4
			self.already_turning = True
		
		# Increase the turning amount if the obstacle is closer to the goblin.
		if self.look_ahead(
			10,
			rectangular_furniture,
			lines ):
			if not self.already_turning:
				self.degrees_to_change = self.change_direction( 3 )
				self.turn_which_way( rectangular_furniture, lines )
			self.deg_amount = 20
			self.already_turning = True

		# Increase the turning amount if the obstacle is closer to the goblin.
		elif self.look_ahead(
			25,
			rectangular_furniture,
			lines ):
			if not self.already_turning:
				self.degrees_to_change = self.change_direction( 2 )
				self.turn_which_way( rectangular_furniture, lines )				
			self.deg_amount = 10
			self.already_turning = True
		elif self.look_ahead(
			75,
			rectangular_furniture,
			lines ):
			if not self.already_turning:
				self.degrees_to_change = self.change_direction( 1 )
				self.turn_which_way( rectangular_furniture, lines )
			self.deg_amount = 4
			self.already_turning = True
		else:
			self.already_turning = False
			self.turn_left = True
			self.turn_right = True
		

		self.collision_detection_rectangle_front.rect.center = self.calculate_collision_detection_center(
			90
		)
		self.collision_detection_rectangle_left.rect.center = self.calculate_collision_detection_center(
			180
		)
		self.collision_detection_rectangle_right.rect.center = self.calculate_collision_detection_center(
			0
		)

		collision_check = self.check_for_collisions(
			self.collision_detection_rectangle_front,
			rectangular_furniture,
			lines
		) 


		if self.degrees_to_change == 0 and not self.already_turning:
			pass
			#self.degrees_to_change = self.change_direction()

		if not collision_check:			
			self.walk()
			self.current_animation = self.animation_walking
		else:
			pass
			#self.current_animation = self.animation_standing
			#self.already_turning = False

		if self.degrees_to_change != 0:
			# Rotate.
			if self.degrees_to_change < 0:
				amount = self.deg_amount
				self.degrees_to_change += self.deg_amount
				if self.degrees_to_change > 0:
					self.degrees_to_change = 0
			else:
				amount = -self.deg_amount
				self.degrees_to_change -= self.deg_amount
				if self.degrees_to_change < 0:
					self.degrees_to_change = 0
			self.rotate( amount )
		else:
			self.already_turning = False		


		# Update all animations.
		self.animation_walking.update()
		self.animation_standing.update()

		# Set the new angle.
		self.angle = self.current_animation.angle		

		# Set the new image and rect.
		self.image = self.current_animation.image		
		self.rect = self.current_animation.rect
		

	def walk( self ):
		"""

		"""
		self.calc_vectors()
		self.position_center[0] += self.vector_x
		self.position_center[1] -= self.vector_y

		self.animation_walking.position_center = self.position_center
		#self.animation_standing.position_center = self.position_center


	def change_direction( self,
		angle_set = 1 ):
		"""Randomly selects a new direction.

		"""
		if angle_set == 1:
			new_angle = random.choice( ( 5, 10, 20, 30 ) )
		elif angle_set == 2:
			new_angle = random.choice( ( 50, 60, 70, 80 ) )
		elif angle_set == 3:
			new_angle = random.choice( ( 160, 170, ) )

		if self.turn_left and self.turn_right:
			new_angle = new_angle * random.choice( ( 1, -1 ) )
		elif self.turn_left:
			new_angle = new_angle
		elif self.turn_right: new_angle * -1
		
		return new_angle


	def rotate( self, degree ):
		"""

		"""
		self.animation_walking.rotate_images( degree )
		self.animation_standing.rotate_images( degree )
		self.angle = self.current_animation.angle


	def calc_vectors( self, additional = 0 ):

		self.vector_x = math.cos( math.radians( self.angle - additional ) ) * self.speed
		self.vector_y = math.sin( math.radians( self.angle - additional ) ) * self.speed


	def calculate_collision_detection_center( self, additional_angle_amount ):
		"""

		"""
		x = ( 5 * math.sin( math.radians( self.angle + additional_angle_amount ) ) + self.rect.centerx )
		y = ( 5 * math.cos( math.radians( self.angle + additional_angle_amount ) ) + self.rect.centery )

		return [x, y]


	def check_for_collisions( self,
		collision_rectangle,
		rectangles,
		lines ):
		"""

		"""
		# Check for collision with rectangles.
		for rect in rectangles:
			if rect.colliderect( collision_rectangle.rect ):
				return True

		# Check for collision with lines.
		if self.calculate_collision_with_lines(
			lines,
			collision_rectangle
		):
			return True
		
		return False


	def calculate_collision_with_lines( self,
		lines,
		collision_rectangle ):
		"""

		"""
		col_rect_left = collision_rectangle.rect.left
		col_rect_right = collision_rectangle.rect.right
		col_rect_top = collision_rectangle.rect.top
		col_rect_bottom = collision_rectangle.rect.bottom

		# Line across the top.
		A2top = col_rect_top - col_rect_top
		B2top = col_rect_right - col_rect_left
		C2top = A2top * col_rect_right + B2top * col_rect_top

		# Line across the bottom.
		A2bottom = col_rect_bottom - col_rect_bottom
		B2bottom = col_rect_right - col_rect_left
		C2bottom = A2bottom * col_rect_right + B2bottom * col_rect_bottom

		# Line across the left.
		A2left = col_rect_bottom - col_rect_top
		B2left = col_rect_left - col_rect_left
		C2left = A2left * col_rect_left + B2left * col_rect_top

		# Line across the right.
		A2right = col_rect_bottom - col_rect_top
		B2right = col_rect_right - col_rect_right
		C2right = A2right * col_rect_right + B2right * col_rect_top		

		for l in lines:

			# Check if any collision detection is needed at all.
			if col_rect_right < min( l.x1, l.x2 ): # No collision possible.
				continue
			elif col_rect_bottom < min( l.y1, l.y2 ): # No collision possible.
				continue
			elif col_rect_left > max( l.x1, l.x2 ): # No collision possible.
				continue
			elif col_rect_top > max( l.y1, l.y2 ): # No collision possible.
				continue

			# Mainline
			A1 = l.A1
			B1 = l.B1
			C1 = l.C1


			# TOP
			det = A1 * B2top - A2top * B1
			X = ( B2top * C1 - B1 * C2top ) / det
			Y = ( A1 * C2top - A2top * C1 ) / det

			if min( l.x1, l.x2 ) <= X <= max( l.x1, l.x2 ) and \
				min( l.y1, l.y2 ) <= Y <= max( l.y1, l.y2 ) and \
				min( col_rect_right, col_rect_left ) <= X <= max( col_rect_right, col_rect_left ) and \
				min( col_rect_top, col_rect_top ) <= Y <= max( col_rect_top, col_rect_top ):
				return True


			# BOTTOM
			det = A1 * B2bottom - A2bottom * B1
			X = ( B2bottom * C1 - B1 * C2bottom ) / det
			Y = ( A1 * C2bottom - A2bottom * C1 ) / det

			if min( l.x1, l.x2 ) <= X <= max( l.x1, l.x2 ) and \
				min( l.y1, l.y2 ) <= Y <= max( l.y1, l.y2 ) and \
				min( col_rect_right, col_rect_left ) <= X <= max( col_rect_right, col_rect_left ) and \
				min( col_rect_bottom, col_rect_bottom ) <= Y <= max( col_rect_bottom, col_rect_bottom ):
				return True


			# LEFT
			det = A1 * B2left - A2left * B1
			X = ( B2left * C1 - B1 * C2left ) / det
			Y = ( A1 * C2left - A2left * C1 ) / det

			if min( l.x1, l.x2 ) <= X <= max( l.x1, l.x2 ) and \
				min( l.y1, l.y2 ) <= Y <= max( l.y1, l.y2 ) and \
				min( col_rect_left, col_rect_left ) <= X <= max( col_rect_left, col_rect_left ) and \
				min( col_rect_bottom, col_rect_top ) <= Y <= max( col_rect_bottom, col_rect_top ):
				return True


			# RIGHT
			det = A1 * B2right - A2right * B1
			X = ( B2right * C1 - B1 * C2right ) / det
			Y = ( A1 * C2right - A2right * C1 ) / det

			if min( l.x1, l.x2 ) <= X <= max( l.x1, l.x2 ) and \
				min( l.y1, l.y2 ) <= Y <= max( l.y1, l.y2 ) and \
				min( col_rect_right, col_rect_right ) <= X <= max( col_rect_right, col_rect_right ) and \
				min( col_rect_bottom, col_rect_top ) <= Y <= max( col_rect_bottom, col_rect_top ):

				return True				



								
			
		return False


	def pick_new_walking_time( self ):
		"""

		"""
		self.walking_time_in_frames = random.randint(
			self.walking_time_in_frames_min,
			self.walking_time_in_frames_max
		)


	def turn_which_way( self, furniture, lines ):
		"""Determines which way the goblin should turn if an obstacle is ahead.

		"""

		self.turn_left = False
		self.turn_right = False

		if not self.check_for_collisions(
			self.collision_detection_rectangle_left,
			furniture,
			lines ):
			self.turn_left = True

		if not self.check_for_collisions(
			self.collision_detection_rectangle_right,
			furniture,
			lines ):
			self.turn_right = True


		


	def look_ahead( self,
		pixels,
		furniture,
		lines
		):
		"""Returns True if there is an obstance n pixels ahead..

		"""

		own_position = self.rect.center
		forecast_position = self.calculate_forecast_rect_center(
			90,
			pixels
		)

		# Create a rectangle and see if it collides with furnitures or walls:
		sprite = pygame.sprite.Sprite()
		sprite.image = pygame.Surface( ( 50, 50 ) )
		sprite.rect = sprite.image.get_rect( center = forecast_position )

		if self.check_for_collisions(
			sprite,
			furniture,
			lines ):
			return True
		else:
			return False


	def watch_out_for_projectiles( self,
		pixels,
		projectiles ):
		"""Returns True if there is an obstacle n pixels ahead.

		"""
		own_position = self.rect.center
		forecast_position = self.calculate_forecast_rect_center(
			90,
			pixels
		)

		# Create a rectangle and see if it collides with furnitures or walls:
		sprite = pygame.sprite.Sprite()
		sprite.image = pygame.Surface( ( 200, 20 ) )
		sprite.rect = sprite.image.get_rect( center = forecast_position )

		# Check for collision with rectangles.
		for rect in projectiles:
			if rect.colliderect( sprite.rect ):
				return True
		return False	

		
		


	def calculate_forecast_rect_center( self, additional_angle_amount, distance ):
		"""

		"""
		x = int( ( distance * math.sin( math.radians( self.angle + additional_angle_amount ) ) + self.rect.centerx ) )
		y = int( ( distance * math.cos( math.radians( self.angle + additional_angle_amount ) ) + self.rect.centery ) )

		return [x, y]


	def update_dying( self ):
		"""Let's the sprite disappear from the screen by drawing pixels more and
		more transparent.

		Once it is not visible anymore, it is removed from the sprite group.
		
		"""
		if self.alpha_dying == 0:
			return True
		else:
			self.alpha_dying -= 5
			self.image.set_alpha( self.alpha_dying )
			return False
